﻿using System;
using System.Windows.Forms;
using MoneyWentWhere.DataModel;
using System.Drawing;
using System.ComponentModel;
using System.Diagnostics;

namespace MoneyWentWhere.WinFormsUI.UI.DataGrid
{
    public class AutoCompleteEditingControl<PanelT>
        : DataGridViewTextBoxEditingControl, IAutoCompletePopupHost
        where PanelT : Control, IAutoCompletePopup
    {
        /* TODO:
         * - Show: on keystrokes (while passing required ones)
         * - Hide: on cell change, 
         * - Move: when control is scrolled, form moved etc. Hide when out of view.
         * 
         * P@:
         * - Pop up: on button press
         */

        static AutoCompleteForm<PanelT> _popup;
        bool _subscribed = false;

        public AutoCompleteEditingControl()
        {
            Trace.WriteLine("ctor of AutoCompleteEditingControl");
        }

        #region Popup manipulation

        static AutoCompleteForm<PanelT> Popup
        {
            get
            {
                if (_popup == null) { _popup = new AutoCompleteForm<PanelT>(); }
                return _popup;
            }
        }

        void UpdatePopupLocation()
        {
            if (!Popup.Visible) { return; }
            if (IsDisposed) { return; }

            // Consider subtle tweaking
            Point pt = PointToScreen(new Point(Left, Bottom));

            // Nudge to right to align with character
            int borderWidth = (Popup.Width - Popup.ClientSize.Width) / 2;
            pt.X -= borderWidth + 6;

            // Pop *above* the field if at the bottom of the screen
            if (pt.Y + Popup.Height > Screen.FromPoint(pt).WorkingArea.Bottom)
            {
                pt = PointToScreen(new Point(Left, Top - Popup.Height - borderWidth));
            }

            // Shift to the right if it hangs off
            int rightEdge = Screen.FromPoint(pt).WorkingArea.Right;
            if (pt.X + Popup.Width > rightEdge)
            {
                pt.X = rightEdge - Popup.Width;
            }

            Popup.Location = pt;
        }

        #endregion


        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (Popup.PopupWantsInputKey(e.KeyData))
            {
                e.SuppressKeyPress = true;
                Popup.OnHostKeyDown(e);
            }
            base.OnKeyDown(e);
        }
        protected override void OnKeyUp(KeyEventArgs e)
        {
            if (Popup.PopupWantsInputKey(e.KeyData))
            {
                e.SuppressKeyPress = true;
            }
            base.OnKeyUp(e);
        }

        protected override bool IsInputKey(Keys keyData)
        {
            // Handling keys here is a bit dirty, but works.
            // If we need to switch focus on tab, may become a different issue
            if (keyData == Keys.Enter || keyData == Keys.Tab)
            {
                if (Popup.Visible) 
                { 
                    Popup.CommitValue();
                    Popup.ClosePopup();
                    return true;
                }
            }
            return base.IsInputKey(keyData);
        }

        public override bool EditingControlWantsInputKey(
            Keys key, bool dataGridViewWantsInputKey)
        {
            // This is almost certainly in the wrong place, but works
            if (!_subscribed) { Subscribe(); }

            bool baseWantsKey = base.EditingControlWantsInputKey(key, dataGridViewWantsInputKey);

            if (Popup.Visible)
            {
                if (key == Keys.Escape ||
                    key == (Keys.Alt | Keys.Up))
                {
                    Popup.ClosePopup();
                    return true;
                }
            }
            else
            {
                if (IsChar(key) ||
                    key == (Keys.Alt | Keys.Down))
                {
                    Popup.ShowPopup(this);
                    UpdatePopupLocation();
                    return true;
                }
            }

            return baseWantsKey || Popup.PopupWantsInputKey(key);; 
        }

        static bool IsChar(Keys key)
        {
            return
                // Numeric, alphabet, allow shift
                (key >= Keys.D0 && key <= Keys.Z) ||
                (key >= (Keys.D0 | Keys.Shift) && key <= (Keys.Z | Keys.Shift)) ||
                // Punctuation
                (key >= Keys.Oem1 && key <= Keys.OemBackslash) ||
                // Other
                key == Keys.Space || key == Keys.Back || key == Keys.Delete;
        }

        #region Handle UI events on parent components

        void Subscribe()
        {
            if (_subscribed) { return; }

            EditingControlDataGridView.CurrentCellChanged += OnDataGridCurrentCellChanged;
            EditingControlDataGridView.Leave += OnDataGridLeave;
            EditingControlDataGridView.MouseClick += OnDataGridClick;

            Control c = this.Parent;
            while (c != null)
            {
                c.LocationChanged += OnLocationChanged;
                if (c is Form)
                {
                    ((Form)c).Deactivate += OnFormDeactivate;
                }
                c = c.Parent;
            }

            _subscribed = true;
        }

        void Unsubscribe()
        {
            EditingControlDataGridView.CurrentCellChanged -= OnDataGridCurrentCellChanged;
            EditingControlDataGridView.Leave -= OnDataGridLeave;
            EditingControlDataGridView.MouseClick -= OnDataGridClick;

            Control c = this.Parent;
            while (c != null)
            {
                c.LocationChanged -= OnLocationChanged;
                if (c is Form)
                {
                    ((Form)c).Deactivate -= OnFormDeactivate;
                }
                c = c.Parent;
            }

            _subscribed = false;
        }

        void OnDataGridClick(object sender, MouseEventArgs e)
        {
            Popup.ClosePopup();
        }

        void OnLocationChanged(object sender, EventArgs e)
        {
            UpdatePopupLocation();
        }

        // Form deactivate hack -- at time of deactivation,
        // ActiveForm field is not updated yet, so we must use
        // a timer and check later. 

        Timer _deactivateTimer;
        Form _mainForm;
        void OnFormDeactivate(object sender, EventArgs e)
        {
            if (_deactivateTimer == null) 
            {
                _mainForm = sender as Form;
                _deactivateTimer = new Timer();
                _deactivateTimer.Interval = 10;
                _deactivateTimer.Tick += DeactivateTimerTick;
            }
            _deactivateTimer.Start();
        }

        void  DeactivateTimerTick(object sender, EventArgs e)
        {
            _deactivateTimer.Stop();
            Form activeForm = Form.ActiveForm; 
            if (activeForm != Popup && 
                activeForm != _mainForm) 
            {
                Popup.ClosePopup();
            }
        }

        void OnDataGridCurrentCellChanged(object sender, EventArgs e)
        {
            Popup.ClosePopup();
            Unsubscribe();
        }

        void OnDataGridLeave(object sender, EventArgs e)
        {
            Popup.ClosePopup();
        }

        #endregion


        protected override void Dispose(bool disposing)
        {
            Unsubscribe();
            base.Dispose(disposing);
        }


        #region IAutoCompletePopupHost Members

        public void ReturnFocus()
        {
            Focus();
        }

        public int CaretPosition { get { return SelectionStart; } }

        #endregion
    }




}
